﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License, as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Drawing;
using DocumentFormat.OpenXml.Spreadsheet;
using ExtremeML.Packaging;
using ExtremeML.Sdk.Vml;
using ExtremeML.Spreadsheet.Address;
using ExtremeML.Spreadsheet.HeaderFooter;

namespace ExtremeML.Spreadsheet
{
	// This is a partial class that adds custom functionality to its auto-generated partner.
	public sealed partial class WorksheetWrapper
	{
		#region Custom Properties

		/// <summary>
		/// Gets the containing <see cref="WorksheetPartWrapper"/>.
		/// </summary>
		public WorksheetPartWrapper WorksheetPart
		{
			get { return (WorksheetPartWrapper)Parent; }
		}

		/// <summary>
		/// Gets the associated <see cref="WorksheetCommentsPartWrapper"/>.
		/// </summary>
		public WorksheetCommentsPartWrapper WorksheetCommentsPart
		{
			get { return WorksheetPart.WorksheetCommentsPart; }
		}

		#endregion
		#region Custom Methods

		/// <summary>
		/// Inserts the specified number of empty rows before the specified row index.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the row that follows the insertion point.</param>
		/// <param name="rowCount">The number of rows to be inserted.</param>
		public void InsertRows(
			int rowIndex,
			int rowCount)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (rowCount < 0) throw new ArgumentOutOfRangeException("rowCount");

			var addresses = new List<IAddressDependency>(WorksheetPart.AddressDependencies);

			// Notify address dependency subscribers of the insertion
			foreach (var address in addresses)
			{
				address.NotifyRowInsertion(rowIndex, rowCount);
			}

			// Propagate defined cell styles from the row above the insertion range
			if (rowIndex > 0 && SheetData.Rows.Contains(rowIndex - 1))
			{
				foreach (var cell in SheetData.Rows.Find(rowIndex - 1).Cells)
				{
//ToDo: the next line should also ignore cells that are part of a table
					if (cell.StyleIndex.HasValue)
					{
						for (var i = rowIndex; i < rowIndex + rowCount; i++)
						{
							SetCellStyleIndex(new GridReference(i, cell.CellAddress.ColumnIndex.Value), cell.StyleIndex);							
						}
					}
				}				
			}
		}

		/// <summary>
		/// Deletes the specified number of rows, starting at the specified row index.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the first row to be deleted.</param>
		/// <param name="rowCount">The number of rows to be deleted.</param>
		public void DeleteRows(
			int rowIndex,
			int rowCount)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (rowCount < 0) throw new ArgumentOutOfRangeException("rowCount");

			var addresses = new List<IAddressDependency>(WorksheetPart.AddressDependencies);

			foreach (var address in addresses)
			{
				address.NotifyRowDeletion(rowIndex, rowCount);
			}
		}

		/// <summary>
		/// Inserts the specified number of empty columns before the specified column index.
		/// </summary>
		/// <param name="columnIndex">The zero-based index of the column that follows the insertion point.</param>
		/// <param name="columnCount">The number of columns to be inserted.</param>
		public void InsertColumns(
			int columnIndex,
			int columnCount)
		{
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");
			if (columnCount < 0) throw new ArgumentOutOfRangeException("columnCount");

			var addresses = new List<IAddressDependency>(WorksheetPart.AddressDependencies);

			foreach (var address in addresses)
			{
				address.NotifyColumnInsertion(columnIndex, columnCount);
			}
		}

		/// <summary>
		/// Deletes the specified number of columns, starting at the specified column index.
		/// </summary>
		/// <param name="columnIndex">The zero-based index of the first column to be deleted.</param>
		/// <param name="columnCount">The number of columns to be deleted.</param>
		public void DeleteColumns(
			int columnIndex,
			int columnCount)
		{
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");
			if (columnCount < 0) throw new ArgumentOutOfRangeException("columnCount");

			var addresses = new List<IAddressDependency>(WorksheetPart.AddressDependencies);

			foreach (var address in addresses)
			{
				address.NotifyColumnDeletion(columnIndex, columnCount);
			}
		}

		/// <summary>
		/// Sets the value of the cell at the specified coordinates to the passed value.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the row containing the target cell.</param>
		/// <param name="columnIndex">The zero-based index of the column containing the target cell.</param>
		/// <param name="value">The value to be assigned to the cell.</param>
		/// <remarks>This method overload is deprecated. You should use the equivalent overload that accepts <see cref="GridReference"/>.</remarks>
		[Obsolete]
		public void SetCellValue(
			int rowIndex,
			int columnIndex,
			object value)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");

			SetCellValue(new GridReference(rowIndex, columnIndex), value);
		}
		
		/// <summary>
		/// Sets the value of the cell at the specified coordinates to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <param name="value">The value to be assigned to the cell.</param>
		public void SetCellValue(
			GridReference address,
			object value)
		{
			GetCell(address, true).SetValue(value);
		}

		/// <summary>
		/// Returns the value of the cell at the specified coordinates, converted to the specified data type.
		/// </summary>
		/// <typeparam name="T">One of the supported data types (see <see cref="CellWrapper"/>).</typeparam>
		/// <param name="rowIndex">The zero-based index of the row containing the target cell.</param>
		/// <param name="columnIndex">The zero-based index of the column containing the target cell.</param>
		/// <returns>The value of the specified cell converted to specified data type.</returns>
		/// <remarks>This method overload is deprecated. You should use the equivalent overload that accepts <see cref="GridReference"/>.</remarks>
		[Obsolete]
		public T GetCellValue<T>(
			int rowIndex,
			int columnIndex)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");

			return GetCellValue<T>(new GridReference(rowIndex, columnIndex));
		}
	
		/// <summary>
		/// Returns the value of the cell at the specified coordinates, converted to the specified data type.
		/// </summary>
		/// <typeparam name="T">One of the supported data types (see <see cref="CellWrapper"/>).</typeparam>
		/// <param name="address">The grid address of the target cell.</param>
		/// <returns>The value of the specified cell converted to specified data type.</returns>
		public T GetCellValue<T>(
			GridReference address)
		{
			var cell = GetCell(address, false);
			return cell == null ? default(T) : cell.GetValue<T>();
		}

		/// <summary>
		/// Sets the formula of the cell at the specified coordinates to the passed value.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the row containing the target cell.</param>
		/// <param name="columnIndex">The zero-based index of the column containing the target cell.</param>
		/// <param name="value">The formula to be assigned to the cell.</param>
		/// <remarks>This method overload is deprecated. You should use the equivalent overload that accepts <see cref="GridReference"/>.</remarks>
		[Obsolete]
		public void SetCellFormula(
			int rowIndex,
			int columnIndex,
			string value)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");

			SetCellFormula(new GridReference(rowIndex, columnIndex), value);
		}

		/// <summary>
		/// Sets the formula of the cell at the specified coordinates to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <param name="value">The formula to be assigned to the cell.</param>
		public void SetCellFormula(
			GridReference address,
			string value)
		{
			GetCell(address, true).SetFormula(value);
		}

		/// <summary>
		/// Gets the formula assigned to the cell at the specified coordinates.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the row containing the target cell.</param>
		/// <param name="columnIndex">The zero-based index of the column containing the target cell.</param>
		/// <returns>The formula assigned to the cell.</returns>
		/// <remarks>This method overload is deprecated. You should use the equivalent overload that accepts <see cref="GridReference"/>.</remarks>
		[Obsolete]
		public string GetCellFormula(
			int rowIndex,
			int columnIndex)
		{
			if (rowIndex < 0) throw new ArgumentOutOfRangeException("rowIndex");
			if (columnIndex < 0) throw new ArgumentOutOfRangeException("columnIndex");

			return GetCellFormula(new GridReference(rowIndex, columnIndex));
		}

		/// <summary>
		/// Gets the formula assigned to the cell at the specified coordinates.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <returns>The formula assigned to the cell.</returns>
		public string GetCellFormula(
			GridReference address)
		{
			var cell = GetCell(address, false);
			return cell == null ? null : cell.GetFormula();
		}

		/// <summary>
		/// Sets the style index of the cell at the specified coordinates to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <param name="value">The style index to be assigned to the cell.</param>
		public void SetCellStyleIndex(
			GridReference address,
			uint? value)
		{
			GetCell(address, true).StyleIndex = value;
		}

		/// <summary>
		/// Gets the style index assigned to the cell at the specified coordinates.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <returns>The style index assigned to the cell.</returns>
		public uint? GetCellStyleIndex(
			GridReference address)
		{
			var cell = GetCell(address, false);
			return cell == null ? null : cell.StyleIndex;
		}

		/// <summary>
		/// Returns the <see cref="CellWrapper"/> that represents the worksheet cell at the
		/// specified coordinates, optionally creating it if it does not already exist.
		/// </summary>
		/// <param name="address">The grid address of the target cell.</param>
		/// <param name="force">A value that indicates whether the cell wrapper should be created if it does not exist.</param>
		/// <returns>The <see cref="CellWrapper"/> associated with the specified worksheet cell.</returns>
		public CellWrapper GetCell(
			GridReference address,
			bool force)
		{
			if (SheetData.Rows.Contains(address.RowIndex))
			{
				return SheetData.Rows.Find(address.RowIndex).GetCell(address.ColumnIndex, force);
			}

			if (force)
			{
				return SheetData.Rows.Add(address.RowIndex).GetCell(address.ColumnIndex, true);
			}

			return null;
		}


		#region Worksheets Comments
		#region Plain Text

		/// <summary>
		/// Sets the value of the comment text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment text.</param>
		public void SetCommentText(
			GridReference address,
			string value)
		{
			SetCommentText(address, value, string.Empty);
		}

		/// <summary>
		/// Sets the value of the comment text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment text.</param>
		/// <param name="author">The name of the comment author.</param>
		public void SetCommentText(
			GridReference address,
			string value,
			string author)
		{
			SetCommentText(address, value, author, null);
		}

		/// <summary>
		/// Sets the value of the comment text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment text.</param>
		/// <param name="author">The name of the comment author.</param>
		/// <param name="size">A <see cref="Size"/> that determines size of the comment shape.</param>
		public void SetCommentText(
			GridReference address,
			string value,
			string author,
			Size size)
		{
			SetCommentText(address, value, author, new ShapeProperties(null, null, size));
		}

		/// <summary>
		/// Sets the value of the comment text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment text.</param>
		/// <param name="author">The name of the comment author.</param>
		/// <param name="properties">A <see cref="ShapeProperties"/> that determines presentation of the comment shape.</param>
		public void SetCommentText(
			GridReference address,
			string value,
			string author,
			ShapeProperties properties)
		{
			GetComment(address, true).SetText(value, author, properties);
		}

		/// <summary>
		/// Returns the value of the comment text at the specified address.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <returns>The text value of the specified comment.</returns>
		public string GetCommentText(
			GridReference address)
		{
			var comment = GetComment(address, false);
			return comment == null ? null : comment.GetText();
		}

		#endregion
		#region Rich Text

		/// <summary>
		/// Sets the value of the comment rich text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment rich text.</param>
		public void SetCommentRichText(
			GridReference address,
			string value)
		{
			SetCommentRichText(address, value, string.Empty);
		}

		/// <summary>
		/// Sets the value of the comment rich text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment rich text.</param>
		/// <param name="author">The name of the comment author.</param>
		public void SetCommentRichText(
			GridReference address,
			string value,
			string author)
		{
			SetCommentRichText(address, value, author, null);
		}

		/// <summary>
		/// Sets the value of the comment rich text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment rich text.</param>
		/// <param name="author">The name of the comment author.</param>
		/// <param name="size">A <see cref="Size"/> that determines size of the comment shape.</param>
		public void SetCommentRichText(
			GridReference address,
			string value,
			string author,
			Size size)
		{
			SetCommentRichText(address, value, author, new ShapeProperties(null, null, size));
		}

		/// <summary>
		/// Sets the value of the comment rich text at the specified address to the passed value.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="value">The value to be assigned to the comment rich text.</param>
		/// <param name="author">The name of the comment author.</param>
		/// <param name="properties">A <see cref="ShapeProperties"/> that determines presentation of the comment shape.</param>
		public void SetCommentRichText(
			GridReference address,
			string value,
			string author,
			ShapeProperties properties)
		{
			GetComment(address, true).SetRichText(RunWrapper.Parse(value), author, properties);
		}

		/// <summary>
		/// Returns the value of the comment rich text at the specified address.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <returns>The rich text value of the specified comment.</returns>
		public string GetCommentRichText(
			GridReference address)
		{
			var comment = GetComment(address, false);
			return comment == null ? null : RunWrapper.Combine(comment.GetRichText());
		}

		#endregion
		#region Images

		/// <summary>
		/// Sets the comment image at the specified address to the passed bitmap.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="image">A <see cref="Bitmap"/> to be assigned to the comment.</param>
		public void SetCommentImage(
			GridReference address,
			Bitmap image)
		{
			SetCommentImage(address, image, null, null);
		}

		/// <summary>
		/// Sets the comment image at the specified address to the passed bitmap.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="image">A <see cref="Bitmap"/> to be assigned to the comment.</param>
		/// <param name="width">A nullable <see cref="int"/> that specifies the rendered image width.</param>
		/// <param name="height">A nullable <see cref="int"/> that specifies the rendered image height.</param>
		/// <remarks>
		/// If either <paramref name="width"/> or <paramref name="height"/> is specified, the rendered
		/// image will be scaled to achieve that dimension. If both <paramref name="width"/> and
		/// <paramref name="height"/> are specified, the rendered image will be distorted to
		/// achieve these dimensions. If neither <paramref name="width"/> nor <paramref name="height"/>
		/// are specified, the rendered image will adopt the original dimensions of the passed image.
		/// </remarks>
		public void SetCommentImage(
			GridReference address,
			Bitmap image,
			int? width,
			int? height)
		{
			GetComment(address, true).SetImage(image, width, height);
		}

		/// <summary>
		/// Returns the image of the comment at the specified address.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <returns>A <see cref="Bitmap"/> that represents the image of the specified comment.</returns>
		public Bitmap GetCommentImage(
			GridReference address)
		{
			var comment = GetComment(address, false);
			return comment == null ? null : comment.GetImage();
		}

		#endregion

		/// <summary>
		/// Returns the <see cref="CommentWrapper"/> associated with the worksheet comment at the
		/// specified coordinates, optionally creating it if it does not already exist.
		/// </summary>
		/// <param name="address">The grid address of the target comment.</param>
		/// <param name="force">A value that indicates whether the comment wrapper should be created if it does not exist.</param>
		/// <returns>The <see cref="CommentWrapper"/> associated with the specified worksheet comment.</returns>
		internal CommentWrapper GetComment(
			GridReference address,
			bool force)
		{
			if (WorksheetCommentsPart.Comments.CommentList.Comments.Contains(address))
			{
				return WorksheetCommentsPart.Comments.CommentList.Comments.Find(address);
			}

			if (force)
			{
				return WorksheetCommentsPart.Comments.CommentList.Comments.Add(address);
			}

			return null;
		}

		#endregion
		#region Headers / Footers

		/// <summary>
		/// Returns the header or footer section text specified by the passed arguments.
		/// </summary>
		/// <param name="usage">A <see cref="HeaderFooterUsage"/> that specifies the context from which to retrieve data.</param>
		/// <param name="section">A <see cref="HeaderFooterSection"/> that specifies the header or footer section to return.</param>
		/// <returns>A <see cref="string"/> that contains the specified header or footer text.</returns>
		public string GetHeaderFooterText(
			HeaderFooterUsage usage,
			HeaderFooterSection section)
		{
			switch (usage)
			{
				case HeaderFooterUsage.FirstHeader:
					return HeaderFooter.FirstHeader.GetSectionString(section);
				case HeaderFooterUsage.OddHeader:
					return HeaderFooter.OddHeader.GetSectionString(section);
				case HeaderFooterUsage.EvenHeader:
					return HeaderFooter.EvenHeader.GetSectionString(section);
				case HeaderFooterUsage.FirstFooter:
					return HeaderFooter.FirstFooter.GetSectionString(section);
				case HeaderFooterUsage.OddFooter:
					return HeaderFooter.OddFooter.GetSectionString(section);
				case HeaderFooterUsage.EvenFooter:
					return HeaderFooter.EvenFooter.GetSectionString(section);
				default:
					return string.Empty;
			}
		}

		/// <summary>
		/// Sets the header or footer section text specified by the passed arguments to the passed value.
		/// </summary>
		/// <param name="usage">A <see cref="HeaderFooterUsage"/> that specifies the context from which to retrieve data.</param>
		/// <param name="section">A <see cref="HeaderFooterSection"/> that specifies the header or footer section to return.</param>
		/// <param name="value">The value to assign to the specified header or footer section.</param>
		public void SetHeaderFooterText(
			HeaderFooterUsage usage,
			HeaderFooterSection section,
			string value)
		{
			switch (usage)
			{
				case HeaderFooterUsage.FirstHeader:
					HeaderFooter.FirstHeader.SetSectionString(section, value);
					break;
				case HeaderFooterUsage.OddHeader:
					HeaderFooter.OddHeader.SetSectionString(section, value);
					break;
				case HeaderFooterUsage.EvenHeader:
					HeaderFooter.EvenHeader.SetSectionString(section, value);
					break;
				case HeaderFooterUsage.FirstFooter:
					HeaderFooter.FirstFooter.SetSectionString(section, value);
					break;
				case HeaderFooterUsage.OddFooter:
					HeaderFooter.OddFooter.SetSectionString(section, value);
					break;
				case HeaderFooterUsage.EvenFooter:
					HeaderFooter.EvenFooter.SetSectionString(section, value);
					break;
				default:
					break;
			}
		}

		/// <summary>
		/// Populates the string format placeholders in the specified header or footer section using the passed array of object values.
		/// </summary>
		/// <param name="usage">A <see cref="HeaderFooterUsage"/> that specifies the context from which to retrieve data.</param>
		/// <param name="section">A <see cref="HeaderFooterSection"/> that specifies the header or footer section to return.</param>
		/// <param name="values">An array of <see cref="object"/> containing the values with which to populate placeholders.</param>
		public void PopulateHeaderFooter(
			HeaderFooterUsage usage,
			HeaderFooterSection section,
			params object[] values)
		{
			switch (usage)
			{
				case HeaderFooterUsage.FirstHeader:
					HeaderFooter.FirstHeader.PopulateSectionString(section, values);
					break;
				case HeaderFooterUsage.OddHeader:
					HeaderFooter.OddHeader.PopulateSectionString(section, values);
					break;
				case HeaderFooterUsage.EvenHeader:
					HeaderFooter.EvenHeader.PopulateSectionString(section, values);
					break;
				case HeaderFooterUsage.FirstFooter:
					HeaderFooter.FirstFooter.PopulateSectionString(section, values);
					break;
				case HeaderFooterUsage.OddFooter:
					HeaderFooter.OddFooter.PopulateSectionString(section, values);
					break;
				case HeaderFooterUsage.EvenFooter:
					HeaderFooter.EvenFooter.PopulateSectionString(section, values);
					break;
				default:
					break;
			}
		}

		#endregion
		#endregion
		#region Overrides

		/// <summary>
		/// Implements custom persistence functionality.
		/// </summary>
		protected override void OnCustomRecomposed(Worksheet wrapped)
		{
//Hack: this code is a horrible short-term fix for the SDK's
//failure to sequence child elements in a schema-compliant manner.

			// Only the following child element types are explicitly
			// managed and they must appear in the following sequence
			var sheetData = Wrapped.GetFirstChild<SheetData>();
			var mergeCells = Wrapped.GetFirstChild<MergeCells>();
			var conditionalFormatting = Wrapped.GetFirstChild<ConditionalFormatting>();
			var hyperlinks = Wrapped.GetFirstChild<Hyperlinks>();

			if (mergeCells != null)
			{
				if (sheetData != null)
				{
					wrapped.RemoveAllChildren<MergeCells>();
					wrapped.InsertAfter(mergeCells, sheetData);
				}
			}

			if (conditionalFormatting != null)
			{
				if (mergeCells != null)
				{
					wrapped.RemoveAllChildren<ConditionalFormatting>();
					wrapped.InsertAfter(conditionalFormatting, mergeCells);
				}
				else if (sheetData != null)
				{
					wrapped.RemoveAllChildren<ConditionalFormatting>();
					wrapped.InsertAfter(conditionalFormatting, sheetData);
				}
			}

			if (hyperlinks != null)
			{
				if (conditionalFormatting != null)
				{
					wrapped.RemoveAllChildren<Hyperlinks>();
					wrapped.InsertAfter(hyperlinks, conditionalFormatting);
				}
				else if (mergeCells != null)
				{
					wrapped.RemoveAllChildren<Hyperlinks>();
					wrapped.InsertAfter(hyperlinks, mergeCells);
				}
				else if (sheetData != null)
				{
					wrapped.RemoveAllChildren<Hyperlinks>();
					wrapped.InsertAfter(hyperlinks, sheetData);
				}
			}
		}

		#endregion
		#region Implementation Methods


		#endregion
		#region Static Methods


		#endregion
	}
}
